package org.example.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.example.system.common.constant.SystemConstants;
import org.example.system.common.enums.StatusEnum;
import org.example.system.common.model.Option;
import org.example.system.converter.DeptConverter;
import org.example.system.mapper.SysDeptMapper;
import org.example.system.model.entity.SysDept;
import org.example.system.model.form.DeptForm;
import org.example.system.model.query.DeptQuery;
import org.example.system.model.vo.DeptVO;
import org.example.system.service.SysDeptService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 部门业务实现类
 *
 * @author LY
 */
@Service
@RequiredArgsConstructor
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> implements SysDeptService {
    private final DeptConverter deptConverter;

    /**
     * 递归生成部门表格层级列表
     *
     * @param parentId 父部门ID，用于指定当前层级的父节点
     * @param deptList 部门列表，包含所有部门的详细信息
     * @return 返回一个包含部门层级结构的Option列表，用于生成表格或树形结构
     */
    public static List<Option> recurDeptTreeOptions(Long parentId, List<SysDept> deptList) {
        // 防御性处理：避免null导致异常
        List<SysDept> safeDeptList = CollectionUtil.emptyIfNull(deptList);

        if (parentId == null) {
            return Collections.emptyList();
        }

        // 构建 parentId -> 子部门列表的映射，提升查询效率
        Map<Long, List<SysDept>> parentToChildrenMap = safeDeptList.stream()
                .filter(dept -> dept.getParentId() != null)
                .collect(Collectors.groupingBy(SysDept::getParentId));

        return buildOptions(parentId, parentToChildrenMap);
    }

    /**
     * 根据父ID和父ID到子部门的映射构建选项列表
     *
     * @param parentId 部门的父ID，用于查找其子部门
     * @param parentToChildrenMap 父ID到子部门列表的映射
     * @return 返回一个Option对象的列表，每个Option代表一个部门及其可能的子部门
     */
    private static List<Option> buildOptions(Long parentId, Map<Long, List<SysDept>> parentToChildrenMap) {
        if (parentToChildrenMap == null) {
            throw new IllegalArgumentException("参数 parentToChildrenMap 不能为 null");
        }

        // 获取给定父ID下的所有子部门，如果没有找到，则返回一个空列表
        List<SysDept> children = parentToChildrenMap.getOrDefault(parentId, Collections.emptyList());

        // 将子部门列表转换为Option对象列表
        return children.stream()
                .map(dept -> createOptionWithChildren(dept, parentToChildrenMap))
                .collect(Collectors.toList());
    }

    /**
     * 创建一个带有子选项的 Option 对象
     * 此方法用于将给定的部门对象和其子部门转换为一个 hierarchical 结构的 Option 对象
     *
     * @param dept 部门对象，不能为 null
     * @param parentToChildrenMap 父部门到子部门列表的映射
     * @return 返回一个带有子选项的 Option 对象
     * @throws IllegalArgumentException 如果 dept 参数为 null，则抛出此异常
     */
    private static Option createOptionWithChildren(SysDept dept, Map<Long, List<SysDept>> parentToChildrenMap) {
        // 检查 dept 参数是否为 null，如果为 null，则抛出 IllegalArgumentException 异常
        if (dept == null) {
            throw new IllegalArgumentException("部门对象 dept 不能为 null");
        }

        // 创建一个 Option 对象，使用部门的 ID 作为选项值，部门名称作为选项标签
        Option option = new Option(dept.getId(), dept.getName());
        // 构建当前部门的子选项列表
        List<Option> children = buildOptions(dept.getId(), parentToChildrenMap);
        // 如果子选项列表不为空，则将其设置为当前 Option 对象的子选项
        if (!children.isEmpty()) {
            option.setChildren(children);
        }
        // 返回带有子选项的 Option 对象
        return option;
    }

    /**
     * 获取部门列表
     * 根据查询条件获取部门列表，并返回树形结构的部门信息。
     *
     * @param queryParams 查询参数对象，包含关键词和状态等查询条件
     * @return 返回树形结构的部门列表，如果查询条件为空或未找到匹配的部门，则返回空列表
     */
    @Override
    public List<DeptVO> listDepartments(DeptQuery queryParams) {
        // 空检查
        if (queryParams == null) {
            return Collections.emptyList();
        }

        // 查询参数
        String keywords = queryParams.getKeywords();
        Integer status = queryParams.getStatus();

        // 查询数据
        List<SysDept> deptList = this.list(
                new LambdaQueryWrapper<SysDept>()
                        .like(StrUtil.isNotBlank(keywords), SysDept::getName, keywords)
                        .eq(status != null, SysDept::getStatus, status)
                        .orderByAsc(SysDept::getSort)
        );

        if (CollectionUtil.isEmpty(deptList)) {
            return Collections.emptyList();
        }

        // 获取所有部门ID和父节点ID
        Set<Long> deptIds = new HashSet<>();
        Set<Long> parentIds = new HashSet<>();
        for (SysDept dept : deptList) {
            deptIds.add(dept.getId());
            parentIds.add(dept.getParentId());
        }

        // 获取根节点ID（递归的起点），即父节点ID中不包含在部门ID中的节点
        List<Long> rootIds = CollectionUtil.subtractToList(parentIds, deptIds);

        // 添加空检查防止NullPointerException
        if (CollectionUtil.isEmpty(rootIds)) {
            return Collections.emptyList();
        }

        // 递归生成部门树形列表
        return rootIds.stream()
                .flatMap(rootId -> recurDeptList(rootId, deptList).stream())
                .toList();
    }

    /**
     * 递归生成部门树形列表
     * 该方法通过递归的方式，根据给定的父ID和部门列表，生成一个树形结构的部门列表。
     * 首先，将部门列表按父ID进行分组，以便快速查找子部门。然后，调用buildDeptTree方法
     * 构建树形结构。
     *
     * @param parentId 父ID，表示当前层级的父部门ID。如果为null，则返回空列表。
     * @param deptList 部门列表，包含所有部门的集合。如果为空或null，则返回空列表。
     * @return 返回一个树形结构的部门列表，类型为List<DeptVO>。如果输入参数无效，则返回空列表。
     */
    public List<DeptVO> recurDeptList(Long parentId, List<SysDept> deptList) {
        if (CollectionUtil.isEmpty(deptList) || parentId == null) {
            return Collections.emptyList();
        }

        // 将部门按父ID分组，避免重复遍历整个列表
        Map<Long, List<SysDept>> parentToChildren = deptList.stream()
                .collect(Collectors.groupingBy(SysDept::getParentId));

        return buildDeptTree(parentId, parentToChildren);
    }

    /**
     * 构建部门树结构
     * 该函数通过递归方式，根据给定的父部门ID和部门映射关系，构建出部门树结构。
     *
     * @param parentId 父部门ID，用于获取当前层级的部门列表
     * @param parentToChildren 部门映射关系，key为父部门ID，value为该父部门下的子部门列表
     * @return 返回构建好的部门树结构，每个部门节点包含其子部门列表
     */
    private List<DeptVO> buildDeptTree(Long parentId, Map<Long, List<SysDept>> parentToChildren) {
        // 获取当前层级的部门列表，如果不存在则返回空列表
        List<SysDept> currentLevelDept = parentToChildren.getOrDefault(parentId, Collections.emptyList());

        // 将当前层级的部门列表转换为部门VO对象，并递归构建其子部门树
        return currentLevelDept.stream()
                .map(dept -> {
                    // 将部门实体对象转换为部门VO对象
                    DeptVO deptVO = deptConverter.entity2Vo(dept);
                    // 递归获取当前部门的子部门树
                    List<DeptVO> children = buildDeptTree(dept.getId(), parentToChildren);
                    // 将子部门树设置到当前部门VO对象中
                    deptVO.setChildren(children);
                    return deptVO;
                }).collect(Collectors.toList());
    }


    /**
     * 获取部门下拉选项列表。
     * 该方法查询所有状态为启用的部门，并根据部门的父子关系生成树形结构的选项列表。
     * 首先查询所有启用的部门，然后通过部门的父子关系确定根部门，最后递归生成树形结构的选项列表。
     *
     * @return 返回部门下拉选项的List集合，集合中的每个元素代表一个部门选项。如果查询结果为空，则返回空列表。
     */
    @Override
    public List<Option> listDeptOptions() {

        // 查询所有状态为启用的部门，并选择部门ID、父部门ID和部门名称字段，按排序字段升序排列
        List<SysDept> deptList = this.list(new LambdaQueryWrapper<SysDept>()
                .eq(SysDept::getStatus, StatusEnum.ENABLE.getValue())
                .select(SysDept::getId, SysDept::getParentId, SysDept::getName)
                .orderByAsc(SysDept::getSort)
        );
        if (CollectionUtil.isEmpty(deptList)) {
            return Collections.emptyList();
        }

        // 获取所有部门的ID集合
        Set<Long> deptIds = deptList.stream()
                .map(SysDept::getId)
                .collect(Collectors.toSet());

        // 获取所有部门的父部门ID集合
        Set<Long> parentIds = deptList.stream()
                .map(SysDept::getParentId)
                .collect(Collectors.toSet());

        // 通过父部门ID集合与部门ID集合的差集，确定根部门ID集合
        Set<Long> rootIds = parentIds.stream()
                .filter(id -> !deptIds.contains(id))
                .collect(Collectors.toSet());

        // 递归生成部门树形列表，确保 recurDeptTreeOptions 不会返回 null
        List<Option> options = new ArrayList<>();
        for (Long rootId : rootIds) {
            List<Option> treeOptions = recurDeptTreeOptions(rootId, deptList);
            if (treeOptions != null) {
                options.addAll(treeOptions);
            }
        }
        return options;
    }

    /**
     * 获取部门详情
     * 该方法根据部门ID查询部门信息，并将其转换为部门表单对象返回。
     * 如果查询结果为空，则返回一个空的部门表单对象。
     * 在查询过程中，如果发生异常，将抛出运行时异常并附带错误信息。
     *
     * @param deptId 部门ID，用于查询指定部门的唯一标识
     * @return 部门表单对象，包含部门的基本信息。如果查询结果为空，则返回一个空的部门表单对象
     * @throws RuntimeException 如果查询过程中发生异常，将抛出运行时异常
     */
    @Override
    public DeptForm getDeptForm(Long deptId) {
        try {
            // 根据部门ID查询部门信息，并选择需要的字段
            SysDept entity = this.getOne(new LambdaQueryWrapper<SysDept>()
                    .eq(SysDept::getId, deptId)
                    .select(
                            SysDept::getId,
                            SysDept::getName,
                            SysDept::getParentId,
                            SysDept::getStatus,
                            SysDept::getSort
                    ));

            if (entity == null) {
                return new DeptForm();
            }

            return deptConverter.entity2Form(entity);
        } catch (Exception e) {
            throw new RuntimeException("获取部门详情失败", e);
        }
    }

    /**
     * 新增部门
     *
     * @param formData 部门表单，包含部门的基本信息，如名称、父部门ID等
     * @return 部门ID，表示新增部门的唯一标识
     * @throws IllegalArgumentException 如果部门名称为空或已存在
     * @throws IllegalStateException 如果部门保存失败
     */
    @Override
    public Long saveDept(DeptForm formData) {
        // 校验部门名称是否存在
        String name = formData.getName();
        if (name == null) {
            throw new IllegalArgumentException("部门名称不能为空");
        }
        boolean exists = this.exists(new LambdaQueryWrapper<SysDept>()
                .eq(SysDept::getName, name)
        );
        if (exists) {
            throw new IllegalArgumentException("部门名称已存在");
        }

        SysDept entity = deptConverter.form2Entity(formData);

        Long parentId = formData.getParentId();
        String treePath = parentId != null ? generateDeptTreePath(parentId) : "";
        entity.setTreePath(treePath);

        // 保存部门并返回部门ID
        boolean result = this.save(entity);
        if (!result) {
            throw new IllegalStateException("部门保存失败");
        }

        return entity.getId();
    }

    /**
     * 更新部门信息
     *
     * @param deptId   部门ID，用于指定需要更新的部门
     * @param formData 部门表单数据，包含部门的新信息
     * @return 更新后的部门ID
     * @throws IllegalArgumentException 如果部门名称为空
     * @throws IllegalStateException    如果部门名称已存在或部门更新失败
     */
    @Override
    public Long updateDept(Long deptId, DeptForm formData) {
        // 校验部门名称是否存在
        String name = formData.getName();
        if (name == null) {
            throw new IllegalArgumentException("部门名称不能为空");
        }
        long count = this.count(new LambdaQueryWrapper<SysDept>()
                .eq(SysDept::getName, name)
                .ne(SysDept::getId, deptId)
        );
        if (count > 0) {
            throw new IllegalStateException("部门名称已存在");
        }

        SysDept entity = deptConverter.form2Entity(formData);
        entity.setId(deptId);

        Long parentId = formData.getParentId();
        String treePath = generateDeptTreePath(parentId);
        entity.setTreePath(treePath);

        // 保存部门并返回部门ID
        boolean result = this.updateById(entity);
        if (!result) {
            throw new IllegalStateException("部门更新失败");
        }

        return entity.getId();
    }

    /**
     * 根据部门ID批量删除部门及其子部门
     *
     * @param ids 部门ID，多个以英文逗号,拼接字符串
     * @return 是否删除成功，成功返回true，失败抛出异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByIds(String ids) {
        // 删除部门及子部门
        if (StrUtil.isNotBlank(ids)) {
            String[] menuIds = ids.split(",");
            for (String deptId : menuIds) {
                if (StrUtil.isBlank(deptId)) {
                    continue;
                }
                try {
                    this.remove(new LambdaQueryWrapper<SysDept>()
                            .eq(SysDept::getId, deptId)
                            .or()
                            .apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", deptId));
                } catch (Exception e) {
                    // 记录日志并回滚事务
                    log.error("删除部门失败，部门ID: " + deptId, e);
                    throw new RuntimeException("删除部门失败", e);
                }
            }
        }
        return true;
    }

    /**
     * 生成部门树路径
     * 该方法根据给定的父部门ID生成该部门的树路径。树路径以英文逗号分隔，表示从根节点到当前节点的路径。
     * 如果父部门ID为根节点ID，则直接返回该ID作为路径。否则，递归获取父部门的树路径，并将当前部门ID追加到路径末尾。
     *
     * @param parentId 父部门ID，不能为null
     * @return 返回生成的部门树路径，格式为以英文逗号分隔的ID序列，例如："1,2,3"
     * @throws IllegalArgumentException 如果parentId为null，或者父部门不存在
     */
    private String generateDeptTreePath(Long parentId) {
        if (parentId == null) {
            throw new IllegalArgumentException("parentId cannot be null");
        }

        // 如果parentId是根节点ID，则直接返回该ID作为路径
        if (SystemConstants.ROOT_NODE_ID.equals(parentId)) {
            return String.valueOf(parentId);
        } else {
            // 根据parentId获取父部门信息
            SysDept parent = this.getById(parentId);
            if (parent == null) {
                throw new IllegalArgumentException("Parent department with id " + parentId + " does not exist");
            }
            // 返回父部门的树路径，并在末尾追加当前部门ID
            return parent.getTreePath() + "," + parent.getId();
        }
    }
}
